'use strict';

setTimeout(process.exit, 3600 * 1000);

var log4js = require('log4js');
var commandLineArgs = require('command-line-args')
var net = require('net');
var AliOSS = require('ali-oss');
var zlib = require('zlib');
var fs = require('fs');

var optionDefinitions = [
  { name: 'agent_id', alias: 'a', type: String },
  { name: 'access_key', alias: 'k', type: String },
  { name: 'access_secret', alias: 's', type: String },
  { name: 'ips', alias: 'i', type: String },
  { name: 'api_port', alias: 'p', type: Number },
  { name: 'commands', alias: 'c', type: String },
  { name: 'threads', alias: 't', type: Number },
  { name: 'ip_file', alias: 'f', type: String },
  { name: 'timeout', alias: 'o', type: Number },
];

var logger = log4js.getLogger('default');
logger.level = 'debug';
var options = commandLineArgs(optionDefinitions);

if(!(options.agent_id && options.access_key && options.access_secret)) {
	logger.error('no agent_id(-a) or access_key(-k) or access_secret(-s) specified');
	process.exit(-1);
}

var api_port = options.api_port || 4028;
var commands = (options.commands || 'stats,pools,summary').split(/,/) || [];
logger.debug('api_port = %d', api_port);
logger.debug('commands = %s', commands.join(','));

var ossClient = new AliOSS({
	'region': 'oss-cn-beijing',
	'accessKeyId': options.access_key,
	'accessKeySecret': options.access_secret
});
ossClient.useBucket('btcqsb-miner');


// if(!options.ips) {
// 	logger.error('no ips specified (-i), format is x.x.x.x/p,y.y.y.y/q');
// 	process.exit(-1);
// }
var arg_ips = (options.ips || '').split(/,/);
var ips = {};
var failed = false;
for(var arg_ip of arg_ips) {
	if(arg_ip == '') continue;
	var m = arg_ip.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)\/(\d+)$/);
	if(!m) {
		logger.error('invalid ip range: %s', arg_ip);
		failed = true;
		continue;
	}

	var a = +m[1], b = +m[2], c = +m[3], d = +m[4];
	if(a < 0 || a > 255 ||
	   b < 0 || b > 255 ||
	   c < 0 || c > 255 ||
	   d < 0 || d > 255
		) {
		logger.error('invalid ip range: %s', arg_ip);
		failed = true;
		continue;
	}
	var prefix = m[5] * 1;
	if(prefix != 16 && prefix != 24 && prefix != 32) {
		logger.error('invalid ip range: %s, invalid prefix %d which can only be 16,24 or 32', arg_ip, prefix);
		failed = true;
		continue;
	}
	var cc = 0;
	switch(prefix) {
		case 32: ips[ [a,b,c,d].join('.') ] = true; cc = 1; break;
		case 16:
			for(var i = 0;i < 256;i++) {
				for(var j = 1;j < 255;j++) {
					ips[ [a,b,i,j].join('.') ] = true;
					cc++;
				}
			}
			break;
		case 24:
			for(var j = 1;j < 255;j++) {
				ips[ [a,b,c,j].join('.') ] = true;
				cc++;
			}
			break;
	}
	logger.debug('Total %d ips parsed from %s', cc, arg_ip);
}

var arg_ip_file = (options.ip_file || '');
if(arg_ip_file) {
	if(!fs.existsSync(arg_ip_file)) {
		logger.error('file %s not exists', arg_ip_file);
		failed = true;
	} else {
		var file_content = fs.readFileSync(arg_ip_file).toString();
		var lines = file_content.split(/\n/);
		for(var line of lines) {
			line = line.trim();
			if(line.length == 0) continue;

			var m = line.match(/^(\d+\.\d+\.\d+\.\d+)$/);
			if(m) {
				ips[ m[0] ] = true;
				continue;
			}

			m = line.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)\-(\d+)$/);
			if(m) {
				var a = +m[1], b = +m[2], c = +m[3], d = +m[4];
				var e = +m[5];
				for(var j = d;j <= e;j++) {
					ips[ [a,b,c,j].join('.') ] = true;
				} 
				continue;
			}

			logger.error('invalid ip or ip range: %s', line);
			failed = true;
			break;
		}
	}
}
if(failed) {
	process.exit(-1);
}

ips = Object.keys(ips);
logger.info('Total %d ips specified', ips.length);

logger.info('#threads = %d', options.threads);

var stats = {
	try : 0,
	data_ok: 0,
};

function check_ip_cmd(ip, cmd, _cb) {
	var done = false;
	var startAt = new Date();

	function cb() {
		if(timer) {
			clearTimeout(timer);
			timer = null;
		}
		if(done) return;
		done = true;
		if(_cb) _cb({
			ip: ip,
			cmd: cmd,
			data: buf,
			start_at: startAt/1000,
			end_at: new Date()/1000,
		});
	}

	var timer = setTimeout(function() {
		logger.error('%s-%s timeout', ip, cmd);
		buf = null;
		cb();
	}, options.timeout || 2000);

	// 矿机的API端口
	var c = net.connect(api_port, ip, function() {
		//console.log('connected');

		c.write(cmd);
	});

	c.setEncoding('utf-8');

	var buf = '';

	c.on('close', cb);
	c.on('error', console.error);
	c.on('data', function(d) {
		buf += d;
	});
}

function check_ip(ip, cb) {
	// 检查的3个命令
	//var cmds = ['stats', 'pools', 'summary'];
	var cmds = commands.slice(0);
	var startAt = new Date();

	var ret = {};
	stats.try++;
	var has_data = false;
	function go() {
		if(cmds.length == 0) {
			if(has_data) {
				stats.data_ok++;
			}
			if(cb) cb({
				ip: ip,
				ret: ret,
				start_at: startAt/1000,
				end_at: new Date()/1000,
			});
			return;
		}

		var cmd = cmds.shift();
		check_ip_cmd(ip, cmd, function(d) {
			ret[cmd] = d;
			if(d && d.data && d.data.length > 0) has_data = true;
			if(d.data === null) {
				//timeout, clear left cmds
				cmds = [];
			}
			setImmediate(go);
		});
	}
	setImmediate(go);
}

function save_to_oss(data, cb) {
	data = {
		data: data,
		agent_id: options.agent_id,
		at: new Date()/1000,
	};
	var str = JSON.stringify(data, null, 2);
	var now = Math.floor(new Date()/1000);
	var oss_fn = 'todo/collect-' + options.agent_id + '-' + now + '.json.gz';
	var local_fn = '/tmp/collect-' + process.pid + '.json.gz';
	var data = zlib.gzipSync(str);
	fs.writeFileSync(local_fn, data);
	var startAt = new Date();
	ossClient.put(oss_fn, local_fn).then(function(result) {
		logger.debug('oss.put costs %s seconds', (new Date() - startAt)/1000);
		fs.unlinkSync(local_fn);
		cb();
	});
}

function check_all(ips, cb) {
	var startAt = new Date();

	var ret = [];

	var totIps = ips.length;
	var finishedIps = 0;

	function tryDone() {
		if(finishedIps != totIps) {
			return;
		}

		logger.info('Done');
		stats.cost = (new Date() - initStartedAt)/1000;
		logger.debug('stats: %s', JSON.stringify(stats));
		// 这里需要记录并上传到OSS
		//console.log(JSON.stringify(ret, null, 2));
		save_to_oss(ret, cb);
	}

	function go() {
		if(ips.length == 0) {
			tryDone();
			return;
		}

		var ip = ips.shift();
		setImmediate(function() {
			check_ip(ip, function(r) {
				ret.push(r);
				finishedIps++;
				logger.debug('finish %d/%d', finishedIps, totIps);
				//setImmediate(go);
				go();
			});
		});
	}

	// 同时发起256路
	for(var i = 0;i < (options.threads || 256);i++) {
		setImmediate(go);
	}
}

var initStartedAt = new Date();
if(ips.length == 0) {
	logger.error('no ips specified');
	process.exit(-1);
}
check_all(ips, process.exit);


